/*
 * 创建日期 2005-8-18
 *
 * 更改所生成文件模板为
 * 窗口 > 首选项 > Java > 代码生成 > 代码和注释
 */
package com.ctsi.commons.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;

/**
 * @author yueming
 * 
 * 更改所生成类型注释的模板为 窗口 > 首选项 > Java > 代码生成 > 代码和注释
 */
public class XmlValidate {
	private static final Logger log = LoggerFactory.getLogger(XmlValidate.class);

	private boolean isOk = true;

	private final Set<String> errMsg = new HashSet<String>();
	private final LocalResolver lr;
	private final LocalErrorHandler eh;

	public XmlValidate() {
		lr = new LocalResolver(new DefaultHandler());
		eh = new LocalErrorHandler(lr);
	}

	public Document readXmlDocument(String content) throws SAXException, ParserConfigurationException, java.io.IOException {
		if (content == null) {
			log.warn(" content was null, doing nothing");
			return null;
		}
		ByteInputStream bis = new ByteInputStream(content.getBytes(StandardCharsets.UTF_8));
		return readXmlDocument(bis, true, "Internal Content");
	}

	public Document readXmlDocument(URL url) throws SAXException, ParserConfigurationException, java.io.IOException {
		if (url == null) {
			log.warn(" URL was null, doing nothing");
			return null;
		}
		InputStream is = url.openStream();

		Document result = readXmlDocument(is, true, url.toString());

		return result;
	}

	/**
	 * 验证读取xml
	 * @param is  会被关闭
	 * @param docDescription
	 * @return
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 * @throws java.io.IOException
	 */
	public Document readXmlDocument(InputStream is, String docDescription) throws SAXException, ParserConfigurationException, java.io.IOException {
		return readXmlDocument(is, true, docDescription);
	}

	/**
	 * 验证读取xml
	 * @param is  会被关闭
	 * @param validate
	 * @param docDescription
	 * @return
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 * @throws java.io.IOException
	 */
	public Document readXmlDocument(InputStream is, boolean validate, String docDescription) throws SAXException, ParserConfigurationException,
			java.io.IOException {
		if (is == null) {
			log.warn(" InputStream was null, doing nothing");
			return null;
		}
		isOk = true;

		Document document = null;

		// DocumentBuilderFactory factory = new
		// org.apache.xerces.jaxp.DocumentBuilderFactoryImpl();
		// factory.setValidating(validate);
		// if (validate) {
		// factory.setNamespaceAware(true);
		//
		// factory.setAttribute("http://xml.org/sax/features/validation",
		// Boolean.TRUE);
		// factory.setAttribute("http://apache.org/xml/features/validation/schema",
		// Boolean.TRUE);
		//
		// }
		//
		// DocumentBuilder builder = factory.newDocumentBuilder();
		lr.hasDTD = false;
		eh.setDocDescription(docDescription);
		try{
			DocumentBuilder builder = UtilXml.getBuilder(lr, eh, validate, docDescription);

			document = builder.parse(is);

			log.info("has dtd : {}", lr.hasDTD);			
		}finally{
			is.close();
		}

		
		return document;
	}

	public Document readXmlDocument(String content, boolean validate) throws SAXException, ParserConfigurationException, java.io.IOException {
		if (content == null) {
			log.warn(" content was null, doing nothing");
			return null;
		}
		ByteInputStream bis = new ByteInputStream(content.getBytes(StandardCharsets.UTF_8));
		return readXmlDocument(bis, validate, "Internal Content");
	}

	/**
	 * 定位dtd xsd 位置
	 * 
	 * @author yueming
	 * 
	 */
	public static class LocalResolver implements EntityResolver {

		private boolean hasDTD = false;

		private EntityResolver defaultResolver;

		public LocalResolver(EntityResolver defaultResolver) {
			this.defaultResolver = defaultResolver;
		}

		/**
		 * 处理dtd或者schema文件位置
		 * 
		 * 
		 * @param publicId -
		 *            Public ID of DTD
		 * @param systemId -
		 *            System ID of DTD
		 * @return InputSource of DTD
		 */
		public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {

			hasDTD = false;

			log.debug("publicId is {}  systemId is {}", publicId, systemId);
			// 处理schema,查询scheman位置
			int lastSlash = systemId.lastIndexOf("/");
			URL resourceUrl = null;
			String filename = null;

			if (lastSlash > -1) {
				String temStr = systemId.substring(0, lastSlash);
				int secondLast = temStr.lastIndexOf("/");

				if (secondLast != -1) {
					filename = systemId.substring(secondLast + 1);
					log.info("load dtd from :{}", filename);
					resourceUrl = UtilURL.fromResource(filename);

				}
			}

			if (resourceUrl == null) {
				if (lastSlash == -1) {
					filename = systemId;
				} else {
					filename = systemId.substring(lastSlash + 1);
				}
				log.info("load dtd from :{}", filename);
				resourceUrl = UtilURL.fromResource(filename);
			}

			if (resourceUrl == null) {
				if (lastSlash == -1) {
					filename = systemId;
				} else {
					filename = systemId.substring(lastSlash + 1);
				}
				log.info("load dtd from :dtd/{}", filename);
				resourceUrl = UtilURL.fromResource("dtd/".concat(filename));
			}

			if (resourceUrl != null) {
				InputStream resStream = resourceUrl.openStream();
				InputSource inputSource = new InputSource(resStream);

				if (UtilValidate.isNotEmpty(publicId)) {
					inputSource.setPublicId(publicId);
				}
				hasDTD = true;

				return inputSource;
			} else {
				log.warn("local dtd not found from :{}", systemId);
				// 好像有时候会在网站下载??
				return defaultResolver.resolveEntity(publicId, systemId);
				// return null;
			}

		}

		public boolean hasDTD() {
			return hasDTD;
		}
	}

	/**
	 * 定位错误处理
	 * 
	 * @author yueming
	 * 
	 */
	public class LocalErrorHandler implements ErrorHandler {

		private String docDescription;

		private LocalResolver localResolver;

		public LocalErrorHandler(String docDescription, LocalResolver localResolver) {
			this.docDescription = docDescription;
			this.localResolver = localResolver;
		}

		public LocalErrorHandler(LocalResolver localResolver) {

			this.localResolver = localResolver;
		}

		public void error(SAXParseException exception) {
			if (localResolver.hasDTD()) {
				isOk = false;
				errMsg.add("XmlFileLoader: File " + docDescription + " process error. Line: " + String.valueOf(exception.getLineNumber()) + ". Error message: "
						+ exception.getMessage());

			}
		}

		public void fatalError(SAXParseException exception) {
			if (localResolver.hasDTD()) {
				isOk = false;
				errMsg.add("XmlFileLoader: File " + docDescription + " process fatal error. Line: " + String.valueOf(exception.getLineNumber())
						+ ". Error message: " + exception.getMessage());

			}
		}

		public void warning(SAXParseException exception) {
			if (localResolver.hasDTD()) {
				isOk = false;
				errMsg.add("XmlFileLoader: File " + docDescription + " process warning. Line: " + String.valueOf(exception.getLineNumber())
						+ ". Error message: " + exception.getMessage());
			}
		}

		public String getDocDescription() {
			return docDescription;
		}

		public void setDocDescription(String docDescription) {
			this.docDescription = docDescription;
		}
	}

	public void clearErrMsg() {
		isOk = true;
		errMsg.clear();
	}

	public Set<String> getErrMsg() {
		return errMsg;
	}

	/**
	 * @return
	 */
	public boolean isOk() {
		if (!lr.hasDTD) {
			log.warn("no xsd,no check");
		}
		return isOk;
	}

}
